﻿Imports System.CodeDom.Compiler
Imports System.Reflection
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Diagnostics
Imports nscript.CharsetDetector.SimpleHelpers

Public MustInherit Class ScriptEngine
    Public MustOverride Sub ShowError(strMsg As String)
    Public MustOverride Sub ShowMainMethodNotFoundError()
    Protected MustOverride Sub SetCompilerParameters(objCompilerParameters As CompilerParameters)
    Protected MustOverride Function GetProvider() As CodeDomProvider

    Public Shared Function CreateScriptEngine(ByVal strScriptPath As String, ByVal blnConsole As Boolean) As ScriptEngine
        Select Case LCase(Path.GetExtension(strScriptPath))
            Case ".vbx"
                If blnConsole Then
                    Return New VBConsoleScriptEngine
                Else
                    Return New VBWindowsScriptEngine
                End If
            Case ".csx"
                If blnConsole Then
                    Return New CSharpConsoleScriptEngine
                Else
                    Return New CSharpWindowsScriptEngine
                End If
        End Select

        Return Nothing
    End Function

    Protected Function DelegateToSearchCriteria(objMemberInfo As MemberInfo, objKey As Object) As Boolean
        Debug.Print("M: " & objMemberInfo.Name.ToString())
        Return StrComp(objMemberInfo.Name.ToString(), objKey.ToString(), CompareMethod.Text) = 0
    End Function

    Public Function Run(args() As String) As Integer
        Try
            Dim strInputPath As String = Path.GetFullPath(args(0))

            Dim strCode As String = FileEncoding.TryLoadFile(strInputPath)
            If strCode = "" Then
                strCode = File.ReadAllText(strInputPath)
            End If

            Dim reg As New Regex("^[ \t]*#pragma[ \t]+comment[ \t]*\([ \t]*ref[ \t]*,[ \t]*""(.+?)""[ \t]*\)[ \t]*(\r?)$",
                                 RegexOptions.IgnoreCase Or RegexOptions.Multiline)
            Dim colRefMatches As MatchCollection = reg.Matches(strCode)
            strCode = reg.Replace(strCode, "$2")

            Dim objCompilerParameters As CompilerParameters = New CompilerParameters()
            objCompilerParameters.GenerateExecutable = False
            objCompilerParameters.GenerateInMemory = True

            Dim strTempDir As String = Path.GetTempFileName
            If File.Exists(strTempDir) Then
                File.Delete(strTempDir)
            End If

            Directory.CreateDirectory(strTempDir)
            Dim strTempFile As String = Path.Combine(strTempDir, "nscript.dll")
            objCompilerParameters.OutputAssembly = strTempFile

            SetCompilerParameters(objCompilerParameters)
            For Each objMatch As Match In colRefMatches
                objCompilerParameters.ReferencedAssemblies.Add(objMatch.Groups(1).Value)
            Next

            Dim arrCode(0) As String
            arrCode(0) = strCode

            Dim objCodeProvider As CodeDomProvider = GetProvider()
            Dim objCompilerResults As CompilerResults = objCodeProvider.CompileAssemblyFromSource(objCompilerParameters, arrCode)
            Directory.Delete(strTempDir, True)

            If objCompilerResults.Errors.Count > 0 Then
                With objCompilerResults.Errors(0)
                    Dim strOutput As String = strInputPath & "(" & .Line & ", " & .Column & ") : error " & .ErrorNumber & ": " & .ErrorText
                    ShowError(strOutput)
                End With
                Return objCompilerResults.NativeCompilerReturnValue
            Else
                Dim ab As Assembly = objCompilerResults.CompiledAssembly()
                Dim memory_module As [Module] = ab.GetModules()(0)
                Dim main_method As MethodInfo = Nothing
                For Each the_type As Type In memory_module.GetTypes()
                    Debug.Print("T: " & the_type.Name.ToString())
                    Dim main_methods = the_type.FindMembers(
                    MemberTypes.Method,
                    BindingFlags.Public Or BindingFlags.Static,
                    New MemberFilter(AddressOf DelegateToSearchCriteria),
                    "Main")
                    If main_methods.Length > 0 Then
                        main_method = main_methods(0)
                        Exit For
                    End If
                Next

                If main_method Is Nothing Then
                    ShowMainMethodNotFoundError()
                    Return 1
                End If

                Dim objRet As Object = Nothing
                If main_method.GetParameters().Length = 0 Then
                    objRet = main_method.Invoke(Nothing, Nothing)
                Else
                    Dim params(0) As Object
                    params(0) = args
                    objRet = main_method.Invoke(Nothing, params)
                End If

                If IsNumeric(objRet) Then
                    Return CInt(objRet)
                End If
            End If

        Catch ex As TargetInvocationException
            ShowError(ex.InnerException.ToString())
            Return 1
        Catch ex As Exception
            ShowError(ex.ToString())
            Return 1
        End Try

        Return 0
    End Function
End Class
